<template>
  <div :class="{ 'button-block': buttonBlock }" class="common-uploadFile">
    <a-upload
      v-model:file-list="relFileList"
      name="file"
      :show-upload-list="false"
      :before-upload="beforeUpload"
      :max-count="10"
      :list-type="listType"
      :multiple="limit > 1 || limit === 0"
      :accept="listType === 'picture-card' ? '.jpg,.jpeg,.png,gif' : ''"
      :custom-request="customRequest"
      @change="changeFile"
    >
      <div v-if="!readonly">
        <template v-if="listType === 'picture-card'">
          <img v-if="imageUrl && !imgHide" style="width: 100%" :src="imageUrl" alt="avatar" />
          <div v-else>
            <loading-outlined v-if="loading"></loading-outlined>
            <plus-outlined v-else></plus-outlined>
            <div class="ant-upload-text">上传</div>
          </div>
        </template>
        <a-button
          v-else
          :ghost="buttonGhost"
          :type="btnType"
          :disabled="loading || disabled"
          :class="{ 'w-full block custom-upload': buttonBlock }"
          style="padding: 0 12px"
        >
          <div v-if="loading" class="flex items-center justify-center">
            <template v-if="!hideIcon"><LoadingOutlined /> </template> 上传中...
          </div>
          <div v-else class="flex items-center justify-center">
            <template v-if="!hideIcon"><i class="shangchuanwendang icpIconfont"></i> </template> {{ btnName }}
            <span v-if="limit > 0 && showLimit" style="color: #ccc; font-size: 12px"> (数量：{{ limit }}) </span>
          </div>
        </a-button>
      </div>
    </a-upload>
    <template v-if="showFileList">
      <div
        v-for="(item, index) in files"
        :key="item.uid"
        style="display: flex; justify-content: space-between; align-items: center"
      >
        <span class="flex-1 truncate" :title="item.name">{{ item.name }}</span>
        <div v-if="!$slots.default">
          <a-button type="link" style="padding: 5px" @click="filePreview(item)">预览</a-button>
          <a-button type="link" style="padding: 5px" @click="fileDownload(item)">下载</a-button>
          <a-button
            v-if="!(readonly || item.readonly)"
            type="link"
            style="padding: 5px"
            danger
            @click="fileRemove(index)"
            >删除</a-button
          >
        </div>
        <slot v-else :data="item"></slot>
      </div>
    </template>
  </div>
</template>
<script setup lang="ts">
import { ref, watch, toRefs } from 'vue'
import { message, Upload } from 'ant-design-vue'
import { UploadListType } from 'ant-design-vue/es/upload/interface'
import { PlusOutlined, LoadingOutlined } from '@ant-design/icons-vue'
import { UploadAndPreview } from './index'
import { getAppEnvConfig } from '@/utils/env'
import { ButtonType } from 'ant-design-vue/lib/button'
import SparkMD5 from 'spark-md5'
import { http } from '@/utils/axios/init'
import axios from 'axios'
import { throttle } from 'lodash-es'
import { storage } from '@/utils/Storage'
import { ACCESS_TOKEN } from '@/store/mutation-types'

const emits = defineEmits(['change', 'update:fileList', 'imgChange'])
const props = withDefaults(
  defineProps<{
    fileList?: any
    baseApi?: string
    isBigFile?: boolean
    disabled?: boolean
    showFileList?: boolean
    closeShard?: boolean
    shardSize?: number
    types?: any
    btnName?: string
    size?: number
    limit?: number
    readonly?: boolean
    listType?: UploadListType
    savePath?: string
    btnType?: ButtonType
    buttonGhost?: any
    buttonBlock?: any
    showLimit?: boolean
    imgFileId?: string // 仅仅图片时生效， 图片的fileId
    bucket?: string // 文件桶
    remoteReplace?: any
    imgHide?: boolean
    changeAlphaFlag?: boolean // 是否改变透明度
    hideIcon?: boolean
  }>(),
  {
    fileList: [],
    isBigFile: false,
    disabled: false,
    showFileList: false,
    closeShard: false, // 关闭分片上传
    shardSize: 5, // 文件分片大小， 单位Mb
    types: [],
    btnName: '上传文件',
    buttonGhost: false,
    buttonBlock: false,
    size: 200, // 文件大小限制， 单位Mb
    limit: 0,
    listType: 'text',
    savePath: '目录01',
    imgFileId: '',
    showLimit: true,
    bucket: 'yootrial-ctms',
    imgHide: false
  }
)

const baseApi = getAppEnvConfig().VITE_GLOB_API_URL

const {
  isBigFile,
  disabled,
  showFileList,
  closeShard, // 关闭分片上传
  shardSize, // 文件分片大小， 单位Mb
  types,
  btnName,
  buttonGhost,
  buttonBlock,
  size, // 文件大小限制， 单位Mb
  limit,
  listType,
  savePath,
  imgFileId,
  showLimit,
  bucket,
  imgHide
} = toRefs(props)
const useUploadAndPreview = new UploadAndPreview(baseApi)

const imageUrl = ref('')
const loading = ref(false)
const files = ref<any[]>([...props.fileList])

watch(
  () => props.fileList,
  (val) => {
    if (val) {
      files.value = [...val]
    } else {
      files.value = []
    }
  },
  {
    deep: true
  }
)

const throttleFun = throttle(
  () => {
    message.error(`只能上传${limit.value}个文件, 已上传${files.value.length}个文件`)
  },
  100,
  { trailing: false }
)

const beforeUpload = (file, fileList) => {
  if (limit.value !== 0 && files.value.length + fileList.length > limit.value) {
    throttleFun()
    return Upload.LIST_IGNORE
  }
  const arr = file.name.split('.')
  const type = arr[arr.length - 1].toLocaleLowerCase()
  if (type === 'sh' || type === 'exe' || type === 'bat') {
    message.error('禁止上传exe、bat和sh文件!')
    return Upload.LIST_IGNORE
  }
  if (limit.value > 0 && files.value.length >= limit.value) {
    message.warning(`只能上传${limit.value}个文件`)
    return Upload.LIST_IGNORE
  }
  // 文件大小限制
  // 把文件大小转为b
  const limitSizeB = size.value * 1024 * 1024
  if (!isBigFile.value && file.size > limitSizeB) {
    message.warning('文件大小不能超过' + size.value + 'Mb')
    return Upload.LIST_IGNORE
  }
  // 如果未给定格式，那么可以接收所有格式文件
  console.log(types.value, '-----types')
  if (types.value.length === 0) {
    return true
  }
  // 把类型全部转为小写后判断
  let isOk = false
  for (let i = 0; i < types.value.length; i++) {
    if (types.value[i].toLocaleLowerCase() === type || types.value[i] === '其他') {
      isOk = true
      continue
    }
  }
  if (!isOk) {
    message.error(`不支持的文件格式.${type}，请上传${types.value.join('、')}`)
  }
  return isOk || Upload.LIST_IGNORE
}
if (imgFileId.value) {
  imageUrl.value = baseApi + '/doc/Rest/doc/file/filePreview' + '?docId=' + imgFileId.value
}
watch(
  () => props.imgFileId,
  (nval) => {
    if (nval) {
      imageUrl.value = baseApi + '/doc/Rest/doc/file/filePreview' + '?docId=' + nval
    } else {
      imageUrl.value = ''
    }
  }
)

// 删除文件
const fileRemove = (index) => {
  files.value.splice(index, 1)
  emits('update:fileList', files.value)
}

const fileDownload = (file) => {
  useUploadAndPreview.downloadFileApi2({ fileId: file.uid, fileName: file.name })
}

const filePreview = (file) => {
  useUploadAndPreview.previewFile(file.uid, file.name)
}

const getFileMd5ByChunk = (file, chunkSize, _chunkCount) => {
  return new Promise((resolve, reject) => {
    const blobSlice = File.prototype.slice || (File.prototype as any).mozSlice || (File.prototype as any).webkitSlice
    const chunks = Math.ceil(file.size / chunkSize)
    let currentChunk = 0
    const spark = new SparkMD5.ArrayBuffer()
    const fileReader = new FileReader()
    fileReader.onload = function (e) {
      spark.append(e?.target?.result) // Append array buffer
      currentChunk++

      if (currentChunk < chunks) {
        loadNext()
      } else {
        const fileMd5 = spark.end()
        resolve(fileMd5)
      }
    }

    fileReader.onerror = function (e) {
      reject(e)
    }

    const loadNext = () => {
      const start = currentChunk * chunkSize
      const end = start + chunkSize >= file.size ? file.size : start + chunkSize
      fileReader.readAsArrayBuffer(blobSlice.call(file, start, end))
    }

    loadNext()
  })
}
const relFileList = ref<any[]>([])
const changeFile = ({ fileList }) => {
  // 通过fileList里面的文件状态来判断文件是否都上传完毕
  loading.value = !fileList.every((item) => item.status !== 'uploading')
  if (!loading.value) {
    relFileList.value = []
  }
}

const customRequest = async (info: any) => {
  const { file, onSuccess, onError } = info
  if (!file) {
    return
  }
  // 获取用户选择的文件
  const fileSize = file.size
  const chunkSize = shardSize.value * 1024 * 1024
  if (!closeShard.value && file.size >= chunkSize) {
    const fileName = file.name
    // 计算当前选择文件需要的分片数量
    const chunkCount = Math.ceil(fileSize / chunkSize)
    // 获取文件md5
    const fileMd5 = await getFileMd5ByChunk(file, chunkSize, chunkCount)

    // 向后端请求本次分片上传初始化
    const initUploadParams = {
      docName: fileName,
      fileMd5,
      savePath: savePath.value,
      bucket: bucket.value,
      chunkCount
    }
    loading.value = true
    try {
      let promises = []
      let uploadId = ''
      let fisrtDocId = ''
      await http
        .request({ url: `/doc/Rest/doc/file/fragmentUpload`, method: 'post', params: initUploadParams })
        .then((res) => {
          const chunkUploadUrls = res.uploadUrlList || []
          fisrtDocId = res.docId
          uploadId = res.uploadId
          promises = chunkUploadUrls.map((url, i) => {
            // 分片开始位置
            const start = i * chunkSize
            // 分片结束位置
            const end = Math.min(fileSize, start + chunkSize)
            // 取文件指定范围内的byte，从而得到分片数据
            const _chunkFile = file.slice(start, end)

            const form = new FormData()
            form.append('url', url)
            form.append('file', _chunkFile)

            const appinfo = (window as any)._PUBLIC_UTILS_.getAppInfo()
            const orgInfo = storage.get('ORGINFO')
            const headers = {
              'yootrial-account': orgInfo ? orgInfo.accountId : '0',
              'yootrial-app': appinfo ? appinfo.appId : '0',
              'yootrial-timestamp': Date.now(),
              'yootrial-signature': '0',
              Authorization: 'Bearer' + storage.get(ACCESS_TOKEN),
              token: storage.get(ACCESS_TOKEN)
            }
            return axios.post(baseApi + '/doc/Rest/doc/file/uploadPart', form, { headers })
          })
        })

      await Promise.all(promises).catch(() => {
        message.error('上传失败')
        throw '上传失败'
      })

      const res = await http.request({
        url: `/doc/Rest/doc/file/completeUpload`,
        method: 'post',
        params: Object.assign({ uploadId, docId: fisrtDocId }, initUploadParams)
      })

      const arr = file.name.split('.')
      const type = arr[arr.length - 1].toLocaleLowerCase()
      const fileObj = {
        uid: res.docId,
        name: file.name,
        type: type,
        size: file.size
      }
      if (limit.value === 1) {
        files.value = []
        files.value[0] = fileObj
      } else {
        files.value.push(fileObj)
      }
      file.fileId = res.docId
      emits('update:fileList', files.value)
      emits('change', file)
      if (listType.value === 'picture-card') {
        useUploadAndPreview.getBase64(file, (base64Url) => {
          imageUrl.value = base64Url
          emits('imgChange', file.fileId, base64Url)
        })
      }
      onSuccess()
    } catch (err) {
      onError(err)
    } finally {
      // loading.value = false
    }
  } else {
    const formData = new FormData()
    formData.append('file', file)
    formData.append('savePath', savePath.value)
    formData.append('bucket', bucket.value)
    if (props.remoteReplace && props.remoteReplace.orgId) {
      formData.append('orgId', props.remoteReplace.orgId)
    }
    if (props.changeAlphaFlag) {
      formData.append('changeAlphaFlag', 'true')
    }
    loading.value = true
    try {
      const res = await useUploadAndPreview.uploadFileApi(formData)

      const arr = file.name.split('.')
      const type = arr[arr.length - 1].toLocaleLowerCase()
      const fileObj = {
        uid: res,
        name: file.name,
        type: type,
        size: file.size
      }
      if (limit.value === 1) {
        files.value = []
        files.value[0] = fileObj
      } else {
        files.value.push(fileObj)
      }
      file.fileId = res
      emits('update:fileList', files.value)
      emits('change', file)

      if (listType.value === 'picture-card') {
        useUploadAndPreview.getBase64(file, (base64Url) => {
          imageUrl.value = base64Url
          emits('imgChange', file.fileId, base64Url)
        })
      }
      onSuccess()
    } catch (err) {
      onError(err)
    } finally {
      // loading.value = false
    }
  }
}
</script>
<style scoped lang="less">
.button-block {
  :deep(.ant-upload.ant-upload-select) {
    display: block;
  }
}
.custom-upload {
  border: 1px dashed #d0d2d4;
  box-shadow: none;
  color: @text-color-secondary;
  &:hover {
    background: #f5f8ff;
    border: 1px dashed @primary-color;
    color: @primary-color;
  }
}
.common-uploadFile {
  :deep(.ant-upload.ant-upload-select-picture-card) {
    overflow: hidden;
  }
}
</style>
